/*   Copyright © 2009 Mark Gil Torres
 *
 *   This file is part of mtoolkit.
 *
 *   mtoolkit is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   any later version.
 *
 *   mtoolkit is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with mtoolkit.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef MT_GRAMMAR_HPP
#define MT_GRAMMAR_HPP

#include <iostream>
#include <string>
#include <stack>

#include <boost/spirit.hpp>
#include "variant.hpp"
#include "mt_actions.hpp"

using namespace mt;
using namespace boost::spirit;
using namespace boost;
using namespace std;

struct MGrammar : public grammar<MGrammar> {

  MGrammar(stack<Variant>& inStack) : myStack(inStack) {}

  template <typename ScannerT>
  struct definition {
    definition(MGrammar const& self) :
      MINUS('-'), PLUS('+'),
      LBRACE('{'), RBRACE('}'), LPAREN('('), RPAREN(')'), LSBRACK('['), RSBRACK(']'),
      ZERO('0'), AMP('&'), DOT('.'), COMMA(','), EXP('^'), SUB('_')
    {
      keywords =
        "\\left", "\\right", "\\begin", "\\end"
        "\\cdot", "\\times", "\\div",
        "\\frac", "\\sqrt", "\\sum", "\\prod", "\\int",
        "\\gcd", "\\min", "\\max", "\\det", "\\lim", "\\log",
        "array",
        "lcm", "\\\\";

      //tokens
      LEFT = strlit<>("\\left");
      RIGHT = strlit<>("\\right");
      BEGIN = strlit<>("\\begin");
      END = strlit<>("\\end");

      CDOT = strlit<>("\\cdot");
      TIMES = strlit<>("\\times");
      DIV = strlit<>("\\div");

      FRAC = strlit<>("\\frac");
      SQRT = strlit<>("\\sqrt");
      SUM = strlit<>("\\sum");
      PROD = strlit<>("\\prod");
      INT = strlit<>("\\int");
      GCD = strlit<>("\\gcd");
      MIN = strlit<>("\\min");
      MAX = strlit<>("\\max");
      DET = strlit<>("\\det");
      LIM = strlit<>("\\lim");
      LOG = strlit<>("\\log");

      ALPHA = strlit<>("\\alpha");
      BETA = strlit<>("\\beta");
      GAMMA = strlit<>("\\gamma");
      DELTA = strlit<>("\\delta");
      EPSILON = strlit<>("\\epsilon");
      VEPSILON = strlit<>("\\varepsilon");
      ZETA = strlit<>("\\zeta");
      ETA = strlit<>("\\eta");
      THETA = strlit<>("\\theta");
      VTHETA = strlit<>("\\vartheta");
      IOTA = strlit<>("\\iota");
      KAPPA = strlit<>("\\kappa");
      LAMBDA = strlit<>("\\lambda");
      MU = strlit<>("\\mu");
      NU = strlit<>("\\nu");
      XI = strlit<>("\\xi");
      PI = strlit<>("\\pi");
      VPI = strlit<>("\\varpi");
      RHO = strlit<>("\\rho");
      SIGMA = strlit<>("\\sigma");
      VSIGMA = strlit<>("\\varsigma");
      TAU = strlit<>("\\tau");
      UPSILON = strlit<>("\\upsilon");
      PHI = strlit<>("\\phi");
      VPHI = strlit<>("\\varphi");
      CHI = strlit<>("\\chi");
      PSI = strlit<>("\\psi");
      OMEGA = strlit<>("\\omega");

      CGAMMA = strlit<>("\\Gamma");
      CDELTA = strlit<>("\\Delta");
      CTHETA = strlit<>("\\Theta");
      CLAMBDA = strlit<>("\\Lambda");
      CXI = strlit<>("\\Xi");
      CPI = strlit<>("\\Pi");
      CSIGMA = strlit<>("\\Sigma");
      CUPSILON = strlit<>("\\Upsilon");
      CPHI = strlit<>("\\Phi");
      CPSI = strlit<>("\\Psi");

      ARRAY = strlit<>("array");
      LCM = strlit<>("lcm");
      NEWLINE = strlit<>("\\\\");

      NZ_DIGIT
        = range_p('1','9')
        ;

      INTEGER_CONST1
        = lexeme_d[NZ_DIGIT >> *digit_p]
        ;

      INTEGER_CONST
        = ZERO
          | INTEGER_CONST1
          | (MINUS >> INTEGER_CONST1)
        ;

      RATIONAL_CONST
        = (FRAC >> LBRACE >> INTEGER_CONST[pushZi(self.myStack)] >> RBRACE >> LBRACE >> INTEGER_CONST[pushZi(self.myStack)] >> RBRACE)
        ;

//      REAL_CONST
//        = lexeme_d[INTEGER_CONST >> DOT >> (INTEGER_CONST1 | ZERO)]
//            >> !((chlit<>('e') | chlit<>('E') | EXP )
//              >> !(PLUS | MINUS) >> INTEGER_CONST)]
//        ;

// atomic data types of mtoolkit
      atom_int
        = INTEGER_CONST[pushZ(self.myStack)]
        ;

      atom_rational
        = RATIONAL_CONST[pushQ(self.myStack)]
        ;

//      atom_real
//        = REAL_CONST[pushR(self.myStack)]
//        ;

      constant
        = longest_d[ atom_rational
          | atom_int
          ]
        ;

     g_alphabet
        = ALPHA | BETA | GAMMA | DELTA | EPSILON | VEPSILON | ZETA
          | ETA | THETA | VTHETA | IOTA | KAPPA | LAMBDA | MU | NU
          | XI | PI | VPI | RHO | SIGMA | VSIGMA | TAU | UPSILON
          | PHI | VPHI | CHI | PSI | OMEGA
        ;

     subscript_helper
        = +alpha_p | +g_alphabet | +digit_p
        ;


     var_helper1
        = ((EXP >> LBRACE >> constant >> RBRACE)
             | (EXP >> (NZ_DIGIT | ZERO)[pushZ(self.myStack)]))[pushVarExp(self.myStack)]
             >> !((SUB >> LBRACE >> subscript_helper[pushVarSub(self.myStack)] >> RBRACE)
             | (SUB >> (alnum_p | g_alphabet)[pushVarSub(self.myStack)]))
        ;
     var_helper2
        = ((SUB >> LBRACE >> subscript_helper[pushVarSub(self.myStack)] >> RBRACE)
             | (SUB >> (alnum_p | g_alphabet)[pushVarSub(self.myStack)]))
             >> !((EXP >> LBRACE >> constant >> RBRACE)
             | (EXP >> (NZ_DIGIT | ZERO)[pushZ(self.myStack)]))[pushVarExp(self.myStack)]
        ;

      variable
        = (range_p('a','z') | g_alphabet)[pushVarName(self.myStack)]
          >> !(var_helper1 | var_helper2)
        ;

      term
        = (constant[pushTermConst(self.myStack)] >> *(variable)[pushVar(self.myStack)])
          | +(variable)[pushVar(self.myStack)]
        ;

//definition for algebraic expression

      a_expr
        = (term[pushTerm(self.myStack)]
            >> *((PLUS >> term[pushTerm(self.myStack)])
               | (MINUS >> term[pushTerm(self.myStack)])))
        ;

      ae_group
        = ('(' >> a_expr >> ')')
        ;

      ae_mult
        = (a_expr | ae_group) >> +ae_group
        ;

      ae_div
        = ((a_expr | ae_group)
          >> +(DIV >> (a_expr | ae_group)))
        ;

      ae_add
        = ((a_expr | ae_group)
          >> +(PLUS >> (ae_group)))
        ;

      ae_subt
        = ((a_expr | ae_group)
          >> +(MINUS >> (ae_group)))
        ;

      ae_operation
        = longest_d[ a_expr[pushAE(self.myStack)]
          | ae_mult
          | ae_div
          | ae_add
          | ae_subt
          ]
        ;

      ae_op_group
        =  ae_operation
          | (LPAREN >> ae_operation >> RPAREN)
        ;

      ae
        = ae_op_group
          >> *((PLUS >> ae_op_group)
             | (MINUS >> ae_op_group)
             | (ae_op_group)
             | (DIV >> ae_op_group))
        ;

      r_expr
        = FRAC >> LBRACE >> ae >> RBRACE >> LBRACE >> ae >> RBRACE
        ;


// Z functions

      zfunctions
        = (GCD >> LPAREN >> atom_int >> COMMA >> atom_int >> RPAREN)//[gcdZ(self.myStack)]
          | (LCM >> LPAREN >> atom_int >> COMMA >> atom_int >> RPAREN)//[lcmZ(self.myStack)]
        ;

//radicals
//     radicals
//       = SQRT
//         >> !(LSBRACK >> (atom_int | range_p('a','z')) >> RSBRACK)
//         >> LBRACE >> arithmetic_expr >> RBRACE
//       ;

//matrix
      matrix
        = (LEFT >> LSBRACK
          >> BEGIN >> LBRACE >> ARRAY >> RBRACE >> !(LBRACE >> +(ch_p('c') | 'l' | 'r') >> RBRACE)
          >> *(atom_int >> *(AMP >> atom_int) >> NEWLINE)
          >> atom_int >> *(AMP >> atom_int)
          >> END >> LBRACE >> ARRAY >> RBRACE
          >> RIGHT >> RSBRACK)
          | (LEFT >> LPAREN >> matrix_expr >> RIGHT >> RPAREN)
        ;

      matrix_expr1
        = matrix
          >> *((PLUS >> matrix)
          | (MINUS >> matrix)
          | (TIMES >> matrix))
        ;

      matrix_expr2
        = (atom_int >> TIMES >> matrix)
          | (matrix
            >> ((TIMES >> atom_int) | (DIV >> atom_int)))
        ;

      matrix_expr
        = matrix_expr1
          | matrix_expr2
        ;

//expression
      expr
        = ae
          | r_expr
//          | radicals
//          | zfunctions
//          | matrix_expr
//          | assignment
        ;

//      assignment
//        = ( vars[setVar(self.var)]
//          | ((alpha_p >> *('_' >> (INTEGER_CONST1 | alpha_p)))[vars.add] >> nothing_p) // will match never, but declares var
//          | vars[setVar(self.var)])
//          >> '=' >> expr[doAssign(self.var,self.myStack)]
//        ;


    }
    symbols<> keywords;
    symbols<Variant> vars;
    chlit<> MINUS, PLUS,
            LBRACE, RBRACE, LPAREN, RPAREN, LSBRACK, RSBRACK,
            ZERO, AMP, DOT, COMMA, EXP, SUB;
    rule<ScannerT> NZ_DIGIT, INTEGER_CONST1, INTEGER_CONST, RATIONAL_CONST;
    rule<ScannerT> atom_int, atom_rational, constant, g_alphabet, subscript_helper, var_helper1, var_helper2, variable,
                   term, a_expr, ae_group, ae_mult, ae_div, ae_add, ae_subt, ae_operation, ae_op_group, ae,
                   r_expr,
                   zfunctions,
                   radicals,
                   matrix, matrix_expr1, matrix_expr2, matrix_expr,
                   assignment,
                   expr;
    rule<ScannerT> LEFT, RIGHT, BEGIN, END,
                   CDOT, TIMES, DIV,
                   FRAC, SQRT, SUM, PROD, INT,
                   GCD, MIN, MAX, DET, LIM, LOG,
                   ALPHA, BETA, GAMMA, DELTA, EPSILON, VEPSILON, ZETA, ETA, THETA, VTHETA,
                   IOTA, KAPPA, LAMBDA, MU, NU, XI, PI, VPI, RHO, SIGMA, VSIGMA, TAU, UPSILON,
                   PHI, VPHI, CHI, PSI, OMEGA, CGAMMA, CDELTA, CTHETA, CLAMBDA, CXI, CPI, CSIGMA,
                   CUPSILON, CPHI, CPSI,
                   ARRAY,
                   LCM, NEWLINE;
    rule<ScannerT> const& start() const { return expr; }
  };

  mutable Variant* var;
  stack<Variant>& myStack;
};

#endif
